/*

    OliveChess 0.2.1
 * 
 *  includes some optimizations
  
    TODO:
    Major
        - Improve move generation to reach the 1000 knodes/sec rate (use C oriented structs & objects)
        - Improve position evaluation with the help of bitboards
        - Update clocks correctly => in progress
        - Re-implement UCI info display (depth, best pv, nps etc.) => in progress
        
  
    Medium
        - implement preliminary exploration of the moves / implement selective search 
        - improve command line interpretor (must not end with an exception if the command line is incorrect)
        - Implement an iterative version of alphabeta
    Minor
        - Manage Transposition table
 * - Implement selective search after depth
 * 
 * 
 * 
 * 
    DONE SO FAR:
        - Include a timer Thread to switch off engine after sleep(thinking) time ==>Done
        - Improve time management (implement alphabeta "quick-break") ==> Done
        - implement the do/undo move part ==> Done
        - sort moves by cutoff probability ==> Done (to be tested some more)
        - change the main search loop to use the depth-first method => Done
        - implement a Value Class (extending integer or other) to manage score/mate_in/draw values => Done

  

    OliveChess 0.1 Injector

    This version is my first trial of a chess engine.

    The analysis method is very basic. A NodeInjectorThread inject nodes in a buffer (nodeQueue) whith father/son dependancy. 
    When the level N+1 is started, the level N is available for a 'false' alphabeta search (no sort and no preliminary scout search)
    Then node scores are recalculated using the alphabeta algorithm.

    the two main issues with this methods are:

    -The accuracy: nodes are added to the buffer systematically. The benefit of alphabeta cuttoff comes only after scores of the
    level N has been calculated. So performances are very close to a brutal MinMax algorithm. A clear of cutted-off nodes improve
    the search a little but they are still in the queue and need to be processed anyway.

    -The memory: massive objects are systematically and continuously added to the buffer as long as the think time is not over.
    Statistically there is around 40 different possible moves per position: each time an object is popped out of the queue, 40 are pushed
    back to the queue. So memory usage grows very quickly (NodeSize* 40^plies)
    Most of the time (even for Blitz games) the engine throws an 'Out of memory' exception.
 
*/



using System;
using System.Collections.Generic;
using System.Text;

namespace OliveChess
{
    class OliveChess
    {
        private const String StartFEN = "rnbqkbnr/pppppppp/8/8/8/8/PPPPPPPP/RNBQKBNR w KQkq - 0 1";
        
        // Main Loop
        static void Main(string[] args)
        {
            //ArrayBoardEngine engine = new ArrayBoardEngine();
            Engine engine = new Engine();
            String token = "";
            String cmd;
            String[] parameters;

            while (token != "quit")
            {
                cmd = Console.ReadLine();
                parameters = cmd.Split(' ');
                token = parameters[0];

                switch (token)
                {
                    case "quit":
                        engine.Quit();
                        break;
                    case "stop":
                        engine.Stop();
                        break;
                    case "ponderhit":
                        engine.Ponderhit();
                        break;
                    case "go":
                        Go(engine, parameters);
                        break;
                    case "ucinewgame":
                        engine.SetStartPosition();
                        break;
                    case "isready":
                        System.Console.WriteLine("readyok");
                        break;
                    case "position":
                        Position(engine, parameters);
                        break;
                    case "setoption":
                        SetOption(engine, parameters);
                        break;
                    case "uci":
                        Uci();
                        break;
                    case "debug":
                        engine.SetDebug(parameters[1] == "on");
                        break;
                    case "test":
                        DebugTest.LaunchTests();
                        break;
                    case "bench":
                        DebugBench.LaunchBench();
                        break;
                    default:
                        System.Console.WriteLine("Unknown command: " + cmd);
                        break;
                }
            }
        }

      
        //* uci
        //    tell arrayBoardEngine to use the uci (universal chess interface),
        //    this will be send once as a first command after program boot
        //    to tell the arrayBoardEngine to switch to uci mode.
        //    After receiving the uci command the arrayBoardEngine must identify itself with the "id" command
        //    and sent the "option" commands to tell the GUI which arrayBoardEngine settings the arrayBoardEngine supports if any.
        //    After that the arrayBoardEngine should sent "uciok" to acknowledge the uci mode.
        //    If no uciok is sent within a certain time period, the arrayBoardEngine task will be killed by the GUI.
        private static void Uci()
        {

            StringBuilder message = new StringBuilder();
            message.AppendLine("id name OliveChess 0.2.1");
            message.AppendLine("id author Olivier Herau, France");
            /*
            message.AppendLine("option name Pawn Value type spin default 80 min 0 max 2000");
            message.AppendLine("option name Knight Value type spin default 325 min 0 max 2000");
            message.AppendLine("option name Bishop Value type spin default 325 min 0 max 2000");
            message.AppendLine("option name Rook Value type spin default 500 min 0 max 2000");
            message.AppendLine("option name Queen Value type spin default 975 min 0 max 2000");

            message.AppendLine("option name Smart Timing type check default false");

            /*
            message.AppendLine("option name Hash type spin default 32 min 4 max 1024");
            message.AppendLine("option name NalimovPath type string default <empty>");
            message.AppendLine("option name NalimovCache type spin default 16 min 1 max 512");
            message.AppendLine("option name EGBB Path type string default egbb");
            message.AppendLine("option name EGBB Cache type spin default 16 min 16 max 512");
            message.AppendLine("option name Ponder type check default false");
            message.AppendLine("option name OwnBook type check default true");
            message.AppendLine("option name MultiPV type spin default 1 min 1 max 256");
            message.AppendLine("option name UCI_Chess960 type check default false");
            message.AppendLine("option name UCI_arrayBoardEngineAbout type string default Fruit by Fabien Letouzey, secom/");
            message.AppendLine("option name BookFile type string default book.bin");
            message.AppendLine("option name Time Allocation type spin default 100 min 0 max 400");
            message.AppendLine("option name EGTB type check default true");
            message.AppendLine("option name Verification Search type combo default Always var Always var End");
            message.AppendLine("option name Quiescence Checks type spin default 1 min 1 max 2");
            message.AppendLine("option name Quiescence Checks PV type spin default 2 min 1 max 4");
            message.AppendLine("option name Pruning type combo default Normal var Normal var Aggressive");
            message.AppendLine("option name Use Aspiration search type check default false");
            message.AppendLine("option name Use Fast Eval type check default true");
            message.AppendLine("option name PPExt type check default false");
            message.AppendLine("option name Extend Checks type check default false");
            message.AppendLine("option name Use Search Focus type check default true");
            message.AppendLine("option name Focus Depth type spin default 1 min 0 max 4");
            message.AppendLine("option name Search Focus Ext type check default false");
            message.AppendLine("option name Round Eval type check default false");
            message.AppendLine("option name Play Style type combo default Normal var Cowardly var Passive vasive var Aggressive var Very-Aggressive var Gambit");
            message.AppendLine("option name Pawn Shielding Hard type spin default 100 min 0 max 400");
            message.AppendLine("option name Pawn Shielding Soft type spin default 100 min 0 max 400");
            message.AppendLine("option name Knight Tropism type spin default 100 min 0 max 200");
            message.AppendLine("option name Queen Tropism type spin default 100 min 0 max 200");
            message.AppendLine("option name King Center Endgame type spin default 12 min 0 max 20");
            message.AppendLine("option name King Sideline Penalty type check default false");
            message.AppendLine("option name Pawn Opening type spin default 80 min 0 max 2000");
            message.AppendLine("option name Pawn Endgame type spin default 90 min 0 max 2000");
            message.AppendLine("option name Knight Value type spin default 325 min 0 max 2000");
            message.AppendLine("option name Bishop Value type spin default 325 min 0 max 2000");
            message.AppendLine("option name Rook Value type spin default 500 min 0 max 2000");
            message.AppendLine("option name Queen Value type spin default 975 min 0 max 2000");
            message.AppendLine("option name Use PPP type check default true");
            message.AppendLine("option name PPP Level type spin default 15 min 0 max 40");
            message.AppendLine("option name Start Max PP Level type spin default 140 min 0 max 200");
            message.AppendLine("option name Pawn Protected PP Level type spin default 30 min 0 max 50");
            message.AppendLine("option name Pawn Duo PP Level type spin default 30 min 0 max 50");
            message.AppendLine("option name Rook Behind PP Level type spin default 30 min 0 max 50");
            message.AppendLine("option name Rook Behind Opp PP Level type spin default 30 min 0 max 50");
            message.AppendLine("option name King Attack PP Level type spin default 5 min 0 max 50");
            message.AppendLine("option name King Defend PP Level type spin default 20 min 0 max 50");
            message.AppendLine("option name Pawn Duo type check default false");
            message.AppendLine("option name Pawn Duo Weight type spin default 2 min 0 max 10");
            message.AppendLine("option name Pawn Duo Weight (Endgame) type spin default 1 min 0 max 10");
            message.AppendLine("option name Pawn Duo File a+b or g+h type spin default 1 min 0 max 25");
            message.AppendLine("option name Pawn Duo File b+c or f+g type spin default 2 min 0 max 25");
            message.AppendLine("option name Pawn Duo File c+d or e+f type spin default 3 min 0 max 25");
            message.AppendLine("option name Pawn Duo File d+e type spin default 4 min 0 max 25");
            message.AppendLine("option name Pawn Duo File a+b or g+h (Endgame) type spin default 4 min 0 max");
            message.AppendLine("option name Pawn Duo File b+c or f+g (Endgame) type spin default 4 min 0 max");
            message.AppendLine("option name Pawn Duo File c+d or e+f (Endgame) type spin default 4 min 0 max");
            message.AppendLine("option name Pawn Duo File d+e (Endgame) type spin default 4 min 0 max 25");
            message.AppendLine("option name Pawn Duo Rank 2 type spin default 0 min 0 max 25");
            message.AppendLine("option name Pawn Duo Rank 3 type spin default 0 min 0 max 25");
            message.AppendLine("option name Pawn Duo Rank 4 type spin default 4 min 0 max 25");
            message.AppendLine("option name Pawn Duo Rank 5 type spin default 5 min 0 max 25");
            message.AppendLine("option name Pawn Duo Rank 6 type spin default 6 min 0 max 25");
            message.AppendLine("option name Pawn Duo Rank 7 type spin default 7 min 0 max 25");
            message.AppendLine("option name Use Knight in Hole Bonus type check default true");
            message.AppendLine("option name Use Bishop in Hole Bonus type check default false");
            message.AppendLine("option name Use Bad Bishop type check default false");
            message.AppendLine("option name Use Queen in Hole Bonus type check default false");
            message.AppendLine("option name BISHOP PAIR VS NO MINORS type spin default 50 min 50 max 150");
            message.AppendLine("option name Use piece combos type check default true");
            message.AppendLine("option name QB WORSE THAN QN PENALTY type spin default 10 min 0 max 50");
            message.AppendLine("option name RB BETTER THAN RN BONUS type spin default 10 min 0 max 50");
            message.AppendLine("option name QRB BETTER THAN QRN BONUS type spin default 10 min 0 max 50");
            message.AppendLine("option name B BETTER THAN N BONUS type spin default 10 min 0 max 50");
            message.AppendLine("option name Rook Attack type spin default 7 min 5 max 7");
            message.AppendLine("option name Pieces over queen bonus type spin default 0 min 0 max 50");
            message.AppendLine("option name Rook over Knight endgame bonus type spin default 0 min 0 max 100");
            message.AppendLine("option name Piece Pawn Score Adjustment type check default false");
            message.AppendLine("option name Contempt Factor type spin default 0 min -1000 max 1000");
            */
            message.AppendLine("uciok");

            System.Console.Write(message);
        }

        //* setoption name  [value ]
        //    this is sent to the arrayBoardEngine when the user wants to change the internal parameters
        //    of the arrayBoardEngine. For the "button" type no value is needed.
        //    One string will be sent for each parameter and this will only be sent when the arrayBoardEngine is waiting.
        //    The name of the option in  should not be case sensitive and can inludes spaces like also the value.
        //    The substrings "value" and "name" should be avoided in  and  to allow unambiguous parsing,
        //    for example do not use  = "draw value".
        //    Here are some strings for the example below:
        //       "setoption name Nullmove value true\n"
        //       "setoption name Selectivity value 3\n"
        //       "setoption name Style value Risky\n"
        //       "setoption name Clear Hash\n"
        //       "setoption name NalimovPath value c:\chess\tb\4;c:\chess\tb\5\n"
        public static void SetOption(Engine engine, String[] parameters)
        {
            string name = "";
            string value = "";
            string temp = "";
            int index = 2;
            while(temp != "value" && index<parameters.Length)
            {
                temp = parameters[index++];
                name += temp;
            }
            while (index < parameters.Length)
            {
                value += parameters[index++];
            }

            engine.SetOption(name, value);      
        }

        //* register
        //this is the command to try to register an arrayBoardEngine or to tell the arrayBoardEngine that registration
        //will be done later. This command should always be sent if the arrayBoardEngine	has send "registration error"
        //at program startup.
        //The following tokens are allowed:
        //    * later
        //       the user doesn't want to register the arrayBoardEngine now.
        //    * name 
        //       the arrayBoardEngine should be registered with the name 
        //    * code 
        //       the arrayBoardEngine should be registered with the code
        //    Example:
        //       "register later"
        //       "register name Stefan MK code 4359874324"
        public void Register(String[] parameters)
        {
            switch (parameters[1])
            {
                //    * later
                //       the user doesn't want to register the arrayBoardEngine now.
                case "later":
                    break;
                //    * name 
                //       the arrayBoardEngine should be registered with the name 
                case "name":
                    break;
                //    * code 
                //       the arrayBoardEngine should be registered with the code
                case "code":
                    break;
                default:
                    break;
            }
        }


        //* position [fen  | startpos ]  moves  .... 
        //set up the position described in fenstring on the internal board and
        //play the moves on the internal chess board.
        //if the game was played  from the start position the string "startpos" will be sent
        //Note: no "new" command is needed. However, if this position is from a different game than
        //the last position sent to the arrayBoardEngine, the GUI should have sent a "ucinewgame" inbetween.
        public static void Position(Engine engine, String [] parameters)
        {
            //TODO: The following command is not giving "white to move" but "black to move"
            //          position startpos moves g1f3 e7e5


            //throw new Exception("The method or operation is not implemented.");
            int index = 1;
            String mode = parameters[index++];
            String startPos = "";
            switch (mode)
            {
                case "startpos":
                    startPos = "rnbqkbnr/pppppppp/8/8/8/8/PPPPPPPP/RNBQKBNR w KQkq - 0 1";
                    break;
                case "fen":
                    for(int i=0;i<6;i++) startPos += parameters[index++] ;
                    break;
            }

            Position pos = new Position(startPos);
            
            if((index<parameters.Length) && parameters[index++] == "moves") {
                while(index<parameters.Length) {
                    Move m = new Move(parameters[index++],pos);
                    pos.Play(m);
                    //DEBUG
                    //Console.WriteLine("+move:" + m.ToString());
                }
            }
            //engine.SetPositionFromFEN(startPos, moves);
            engine.SetPosition(pos);
        }

        //* go
        //start calculating on the current position set up with the "position" command.
        //There are a number of commands that can follow this command, all will be sent in the same string.
        //If one command is not send its value should be interpreted as it would not influence the search.
        //
        public static void Go(Engine engine, String[] parameters)
        {
            int index = 1;
            while (index < parameters.Length)
            {
                switch (parameters[index++])
                {
                        //* searchmoves  .... 
                        //    restrict search to this moves only
                        //    Example: After "position startpos" and "go infinite searchmoves e2e4 d2d4"
                        //    the arrayBoardEngine should only search the two moves e2e4 and d2d4 in the initial position.
                    case "searchmoves":
                        List<Move> moves = new List<Move>();
                        while (index < parameters.Length)
                        {
                            Move m = new Move(parameters[index++], engine.GetCurrentPosition());
                            moves.Add(m);                            
                            //DEBUG
                            //Console.WriteLine("+move:" + m.ToString());
                        }
                        engine.SetSearchmovesMode(moves);
                        break;
                        //* ponder
                        //    start searching in pondering mode.
                        //    Do not exit the search in ponder mode, even if it's mate!
                        //    This means that the last move sent in in the position string is the ponder move.
                        //    The arrayBoardEngine can do what it wants to do, but after a "ponderhit" command
                        //    it should execute the suggested move to ponder on. This means that the ponder move sent by
                        //    the GUI can be interpreted as a recommendation about which move to ponder. However, if the
                        //    arrayBoardEngine decides to ponder on a different move, it should not display any mainlines as they are
                        //    likely to be misinterpreted by the GUI because the GUI expects the arrayBoardEngine to ponder
                        //   on the suggested move.
                    case "ponder":
                        engine.SetPonderMode();
                        break;
                        //* wtime 
                        //    white has x msec left on the clock
                    case "wtime":
                        engine.SetWtime(int.Parse(parameters[index++]));
                        break;
                        //* btime 
                        //    black has x msec left on the clock
                    case "btime":
                        engine.SetBtime(int.Parse(parameters[index++]));
                        break;
                        //* winc 
                        //    white increment per move in mseconds if x > 0
                    case "winc":
                        engine.SetWinc(int.Parse(parameters[index++]));
                        break;
                        //* binc 
                        //    black increment per move in mseconds if x > 0
                    case "binc":
                        engine.SetBinc(int.Parse(parameters[index++]));
                        break;
                        //* movestogo 
                        //  there are x moves to the next time control,
                        //    this will only be sent if x > 0,
                        //    if you don't get this and get the wtime and btime it's sudden death
                    case "movestogo":
                        engine.SetMovestogo(int.Parse(parameters[index++]));
                        break;
                        //* depth 
                        //    search x plies only.
                    case "depth":
                        engine.SetDepthMode(int.Parse(parameters[index++]));
                        break;
                        //* nodes 
                        //   search x nodes only,
                    case "nodes":
                        engine.SetNodesMode(int.Parse(parameters[index++]));
                        break;
                        //* mate 
                        //    search for a mate in x moves
                    case "mate":
                        engine.SetMateMode(int.Parse(parameters[index++]));
                        break;
                        //* movetime 
                        //    search exactly x mseconds
                    case "movetime":
                        engine.SetMovetimeMode(int.Parse(parameters[index++]));
                        break;
                        //* infinite
                        //    search until the "stop" command. Do not exit the search without being told so in this mode!
                    case "infinite":
                        engine.SetInfiniteMode();
                        index++;
                        break;
                }
            }
            engine.Start();
        }
    }
}
